package Test.限流算法;

/**
 * @Author: kirito
 * @Description:
 * 令牌桶算法是一种常用的限流算法，可以用于限制单位时间内请求的数量。
 * 该算法维护一个固定容量的令牌桶，每秒钟会向令牌桶中放入一定数量的令牌。
 * 当有请求到来时，如果令牌桶中有足够的令牌，则请求被允许通过并从令牌桶中消耗一个令牌，否则请求被拒绝。
 *
 * 优点：
 *
 * 稳定性高：令牌桶算法可以控制请求的处理速度，可以使系统的负载变得稳定。
 * 精度高：令牌桶算法可以根据实际情况动态调整生成令牌的速率，可以实现较高精度的限流。
 * 弹性好：令牌桶算法可以处理突发流量，可以在短时间内提供更多的处理能力，以处理突发流量。
 * Guava的RateLimiter限流组件，就是基于令牌桶算法实现的。
 *
 * 缺点：
 *
 * 实现复杂：相对于固定窗口算法等其他限流算法，令牌桶算法的实现较为复杂。
 * 对短时请求难以处理：在短时间内有大量请求到来时，可能会导致令牌桶中的令牌被快速消耗完，从而限流。这种情况下，可以考虑使用漏桶算法。
 * 时间精度要求高：令牌桶算法需要在固定的时间间隔内生成令牌，因此要求时间精度较高，如果系统时间不准确，可能会导致限流效果不理想。
 * 总体来说，令牌桶算法具有较高的稳定性和精度，但实现相对复杂，适用于对稳定性和精度要求较高的场景。
 * @Date: 2024/2/28 15:16
 * @Version: 1.0
 */

public class 令牌桶 {
    /**
     * TokenBucket 类表示一个令牌桶
     */
    public class TokenBucket {

        private final int capacity;     // 令牌桶容量
        private final int rate;         // 令牌生成速率，单位：令牌/秒
        private int tokens;             // 当前令牌数量
        private long lastRefillTimestamp;  // 上次令牌生成时间戳

        /**
         * 构造函数中传入令牌桶的容量和令牌生成速率。
         * @param capacity
         * @param rate
         */
        public TokenBucket(int capacity, int rate) {
            this.capacity = capacity;
            this.rate = rate;
            this.tokens = capacity;//初始化令牌桶肯定就是满的嘛，所以大小等于令牌桶的容量
            this.lastRefillTimestamp = System.currentTimeMillis();
        }

        /**
         * allowRequest() 方法表示一个请求是否允许通过，该方法使用 synchronized 关键字进行同步，以保证线程安全。
         * @return
         */
        public synchronized boolean allowRequest() {
            refill();
            if (tokens > 0) {
                tokens--;
                return true;
            } else {
                return false;
            }
        }

        /**
         * refill() 方法用于生成令牌，其中计算令牌数量的逻辑是按照令牌生成速率每秒钟生成一定数量的令牌，
         * tokens 变量表示当前令牌数量，
         * lastRefillTimestamp 变量表示上次令牌生成的时间戳。
         */
        private void refill() {
            long now = System.currentTimeMillis();
            if (now > lastRefillTimestamp) {
                int generatedTokens = (int) ((now - lastRefillTimestamp) / 1000 * rate);
                tokens = Math.min(tokens + generatedTokens, capacity);
                lastRefillTimestamp = now;
            }
        }
    }
}
